﻿#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

//大小端字节序和字节序判断

//int main()
//{
//	int a = 0x11223344;
//	int b = 136;
//    int c = -1;
//	return 0;
//}
    //首先我们知道：1.整数在内存中是以二进制补码的形式存储的 在调试是c为-1，显示的就是ff ff ff ff
    //              2.在调试窗口中观察内存的时候，为了方便展示，将二进制形式转换为十六进制的值进行显示
    //              3.存储的顺序是倒过来的

    //调试的时候，我们可以看到在a中的 0x11223344 这个数字是按照“字节为单位”，倒着存储的。即显示为44 33 22 11
    //这是为什么呢？这里我们首先讲一下大小端是什么？
    //其实超过一个字节的数据在内存中存储的时候，就有存储顺序的问题，按照不同的存储顺序，
	//我们分为大端字节序存储和小端字节序存储，下面是具体的概念：(是需要记住的)

//大端（存储）模式：
    //是指数据的“低位字节”内容保存在内存的“高地址处”，而数据的“高位字节”内容，保存在内存的“低地址处”。

//小端（存储）模式：
    //是指数据的“低位字节”内容保存在内存的“低地址处”，而数据的“高位字节”内容，保存在内存的“高地址处”。

//练习1
    //请简述大端字节序和小端字节序的概念，设计一个小程序来判断当前机器的字节序。
//int main()
//{
//    int a = 0x00000001; //0x00 00 00 01
//    if (*(char*)&a == 1)//首先取出a的第一个字节的地址(低地址)，对其强制类型转换成char*，再解引用此时就只能访问第一个字节内容
//                        //不能直接把a强制类型转换成char，因为判断大小端就是判断低位字节是在高地址还是低地址处
//                        //而直接强制类型转换成char是没有考虑高地址还是低地址一说，都是取整型a的低位字节
//    {
//        printf("小端模式\n"); //小端：01 00 00 00 (左边为低地址处，右边为高地址处)
//    }
//    else
//        printf("大端模式\n"); //大端：00 00 00 01
//    return 0;
//}

//练习2
//int main()
//{
//    char a = -1;
//    signed char b = -1; //在VS中char == signed char
//    unsigned char c = -1;
//    printf("a=%d,b=%d,c=%d", a, b, c); //a=-1， b=-1， c=255
//    return 0;
//}
    //首先-1在内存中以补码形式存储：11111111111111111111111111111111
    //强制类型转换成char时，则从4个字节变成1个字节，即a,b,c在内存中存储d都为11111111
    //signed char是有符号的char类型，最高位是符号位，unsigned char是无符号的char类型，最高位是数值位
    //所以signed char的取值范围是-128(10000000)~127(01111111),unsigned char取值范围是0(00000000)~255(11111111)
    //%d打印时是以有符号数原码形式打印，如果是有符号则需要由补码转换为原码，正数则一样，负数则取反再加1
    //                          如果是无符号就没有原反补之分，全部都以存储的形式直接打印
    //最后需要注意的是，%d是以十进制的形式打印有符号的整数，a,b,c都是char类型，所以要用我们之前学到的东西就是整型提升
    //有符号整数提升是按照变量的数据类型的“符号位”来提升的；无符号整数提升，⾼位直接补0，然后再打印

//练习3
//int main()
//{
//    char a = -128;
//    printf("%u\n", a); //4294967168
//    return 0;
//}
    //首先要知道%u和%d相反，是以十进制打印无符号的整数，不管变量是有符号还是无符号类型都认为是存放无符号数
    //-128的补码为11111111111111111111111110000000
    //所以a为char类型，存放的就是10000000，此时打印时由于a不是整型，所以需要整型提升
    //而a为char，是有符号数，整型提升的最高位以符号位对应数补充：11111111111111111111111110000000
    //然后%u打印时又把a视为无符号数，则11111111111111111111111110000000的最高位为数值位，转换成十进制值为4294967168

//int main()
//{
//    char a = 128;
//    printf("%u\n", a); //4294967168
//    return 0;
//}
    //128的补码：00000000000000000000000010000000
    //10000000 - a
    //a为char，最高位是符号位，整型提升的最高位以符号位对应数补充：11111111111111111111111110000000
    //然后%u打印时又把a视为无符号数，则11111111111111111111111110000000的最高位为数值位，转换成十进制值为4294967168

//int main()
//{
//    char a = 128;
//    printf("%d\n", a); //-128
//    return 0;
//}
    //128的补码：00000000000000000000000010000000
    //10000000 - a
    //a为char，最高位是符号位，整型提升的最高位以符号位对应数补充：11111111111111111111111110000000
    //然后%d打印时把a视为有符号数，则最高位为符号位，此时为负数，则取反加1得到原码再以十进制打印即为-128

//练习4
//int main()
//{
//    char a[1000];
//    int i;
//    for (i = 0; i < 1000; i++)
//    {
//        a[i] = -1 - i;
//    }
//    printf("%d", strlen(a)); //255 --> a[0] = -1 ~ a[254] = -255 (a[255] = -256 -> '\0')
//    return 0;
//} 
    //我们要知道char取出的8个bit位是在以内存中存储的形式(整数补码的形式)的低地址处
    //0的补码为000……000，char 0 则取00000000，加1 -> char 1 则取00000001
    //依此类推，但加到127时，char 127 取为01111111，再加1时到128
    //128的补码为00000000000000000000000010000000，则char 128 取10000000
    //对其%d打印时，由于最高位是符号位，整型提升补对应符号位数：11111111111111111111111110000000(补码)，则原码得到的值为-128
    //char 129 取10000001，整型提升再转换成原码值为-127，
    //以此类推当加到255时，char 255 取11111111，而整型提升为：11111111111111111111111111111111，对应%d打印的值为-1
    //再加1到256时，此时低八位又重新变成0，所以char 256 取00000000，整型提升后%d打印的值就是0

    //以此我们得到了一个 char 整数 的规律：
    //前127的整数char类型%d打印得到的就是对应值，原因就是最高位符号位为0
    //从128开始往后到255，由于此时第八位符号位为1，整型提升后最高位仍是1，%d打印则视为负数，对应值从-128 ~ -1

    //当到256时相当于走了一个循环，也就是char 256 == char 0,再往后加1则会循环往复，所以 char 整数 的范围是 -128 ~ 127

    //再回到这个题，由于之前我们学习了strlen计算字符串长度(也就是字节个数)到'\0'就停止
    //而'\0'对应的ascii值就是0，由于此时是从-1一直递减，则上面讲的循环变成逆序：
    //当减到-129时，char -129 取01111111，整型提升后%d打印的值为127；到-256时则是走完一个循环char -256 == char 0 == 0
    //strlen计算到-256时就会停止，计算'\0'前的长度：255

//练习5
//unsigned char i = 0;
//int main()
//{
//    for (i = 0; i <= 255; i++)
//    {
//        printf("hello world\n"); //死循环
//    }
//    return 0;
//}
    //在前面一题我们已经对char 整数有了深刻理解，而此题无非就是char变成unsigned char
    //我们知道unsigned char是无符号的，所以在整型提升时最高位不管是1还是0，补充的都是0，
    //再%d打印时又视为有符号数打印，所以 unsigned char 整数 %d打印的值均为正数，而范围是 0 ~ 255
    //到256时和上面一题一样重新进行循环，256时 i取的8个bit位为00000000 -> 0 ,所以for循环跳不出256，导致死循环

//int main()
//{
//    unsigned int i;
//    for (i = 9; i >= 0; i--)
//    {
//        printf("%u\n", i); //死循环，和上面一样，unsigned int 整数 也有自己的范围：0 ~ 4294967295
//    }                      //当i由0再减1变成-1时，由于-1的最高位为1，而unsigned int视为无符号数，则最高位为数值位
//    return 0;              //原本i为-1则变成了int类型的最大值
//}

//练习6
//X86环境 小端字节序 
//int main()
//{
//    int a[4] = { 1, 2, 3, 4 };
//    int* ptr1 = (int*)(&a + 1);
//    int* ptr2 = (int*)((int)a + 1);
//    printf("%x,%x", ptr1[-1], *ptr2); //4，2000000
//    return 0;
//}
    //第一个ptr1[-1]在之前已经讲的很清晰了，就不再赘述了
    //而ptr2要仔细思考：首先是(int)a,a为数组名，就是数组首元素的地址，然后将其强制类型转换成了int类型
    //所以此时a虽然还是首元素地址这个内容，但实际只是一个数，加1的话就不再是指针加1，就只是单纯数字加1
    //理解成地址的话相应的就是跳过一个字节，再对其强制类型转换成int*，又回到指针类型
    //数组的1，2，3，4在内存中存储的样子就是前面学习的小端(存储)模式：01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00
    //所以此时(int*)((int)a + 1)所在位置就是01后面的第一个00的位置地址，也就是ptr2的位置
    //而ptr2的类型是int*，对其解引用访问的就是当前位置往后4个字节数，也就是 00 00 00 02
    //又因为是小端(存储)模式，所以*ptr2 -> 0x02 00 00 00 -> %x打印就是2000000

//浮点数在内存中的存储(相较于整型在内存中存储更加繁琐复杂)
//int main()
//{
//    int n = 9;
//    float* pFloat = (float*)&n;
//    printf("n的值为：%d\n", n); //9
//    printf("*pFloat的值为：%f\n", *pFloat); //0.000000
//    *pFloat = 9.0;
//    printf("n的值为：%d\n", n); //1091567616
//    printf("*pFloat的值为：%f\n", *pFloat); //9.000000
//    return 0;
//}
//浮点数的存储
    //通过运行结果我们会发现 n 和 *pFloat 在内存中明明是同一个数，为什么浮点数和整数的解读结果会差别这么大？
    //要理解这个结果，一定要搞懂浮点数在计算机内部的表示方法：

    //通过国际规定，任意一个二进制浮点数 f 可以表示成下面的形式：
    // f = (-1)^S * M ∗ 2^E
    // (-1) 表示符号位，当 S = 0，f为正数；当 S = 1，f为负数
    // M 表示有效数字，M是大于等于1，小于2的
    // 2^E 表示指数位

    //举例来说：
    //十进制的 5.0，写成二进制是 101.0 ，相当于 1.01×2^2(1.01就是有效数字)
    //那么，按照上面 f 的格式，可以得出  S = 0，M = 1.01，E = 2。

    //然后又规定：
    //对于32位的浮点数(float)，最高的1位存储符号位S，接着的8位存储指数E，剩下的23位存储有效数字M
    //对于64位的浮点数(double)，最高的1位存储符号位S，接着的11位存储指数E，剩下的52位存储有效数字M

//浮点数存储的过程
    //对于有效数字 M 和指数 E ，国际还有一些特别规定：

    //对于有效数字 M 来说，前面说过，1≤M<2 ，也就是说，M可以写成 1.xxxxxx 的形式，其中 xxxxxx 表示小数部分。
    //在计算机内部保存 M 时，默认这个数的第一位总是1，因此可以被舍去，只保存后面的 xxxxxx 部分。
    //比如保存1.01的时候，只保存 01 ，等到读取的时候，再把第一位的1加上去。
    //这样做的目的，是节省1位有效数字。以32位浮点数为例，留给 M 只有23位，将第⼀位的1舍去以后，等同于可以保存24位有效数字。

    //至于指数 E ，情况就比较复杂：
    //首先，E 是作为一个无符号整数（unsigned int）
    //这意味着，如果 E 为8位，它的取值范围为0~255；如果 E 为11位，它的取值范围为0~2047。
    //但是，我们知道，科学计数法中的E是可以出现负数的，比如 0.5 --> (-1)^0 * 1 ∗ 2^(-1) --> S = 0，M = 1，E = -1
    //这样就不符合 E 作为无符号整数的要求，所以国际规定了：
    //存入内存时 E 的真实值必须再加上一个中间数，对于8位的 E，这个中间数是127；对于11位的 E，这个中间数是1023。
    //比如，2^10的 E 是10，所以保存成32位浮点数时，必须保存成10+127=137，即10001001。
    //为了快速写出 E 在内存中的存储，由于127就是 01111111，加1就变成了10000000，此时再加上 E - 1，就得到存储形式了

//浮点数取的过程
    //指数E从内存中取出还可以再分成三种情况：

//E不全为0或不全为1（常规情况）
    //这时，浮点数就采用下面的规则表示，即指数 E 的计算值减去127（或1023），得到真实值，再将有效数字M前加上第一位的1。
    //比如：0.5 的二进制形式为 0.1 --> (-1)^0 * 1.0 * 2^(-1)，S = 0, M = 1.0, E = -1
    //则存入内存时 E 的真实值为 -1 + 127 = 126，表示为01111110
    //而 M 为1.0，去掉整数部分为0，补齐0到23位00000000000000000000000
    //则其二进制表示形式为: 0 01111110 00000000000000000000000

//E全为0
    //这时，浮点数的指数 E 规定等于1-127（或者1-1023）即为真实值，有效数字 M 不再加上第⼀位的1，而是还原为0.xxxxxx的小数。
    //这样做是为了表示±0，以及接近于0的很小的数字。

//E全为1
    //这时，如果有效数字 M 全为0，表示±无穷大（正负取决于符号位s）

    //以上就是国际对于浮点数在内存中的存储形式和要求

    //接着我们再回到开始的代码
    //首先我们看int n = 9，9在内存中以整型存储表示为 00000000 00000000 00000000 00001001(整型存储9)
    //以%d打印就是补码转换成原码再十进制打印，结果就是9
    //但将其取地址再强制类型转换成float*，赋给pFloat变量时，站在pFloat变量的视角，会认为自己指向的是一个float类型的数值
    //而float类型的数据在内存中存储方式就是上面所讲的内容：
    //0 00000000 00000000000000000001001
    //此时 E 为全0，真实 E 的值为1-127=-126，有效数字 M 不再加上第⼀位的1
    //所以f = (-1)^0 * 0.00000000000000000001001 * 2^(-126) 此时已经无限接近于0了，所以第二个输出结果为0.000000

    //再看*pFloat = 9.0，把9.0直接赋给*pFloat，由于 *pFloat 是 float 类型，相当于9.0就是以浮点数的形式在内存中存储
    //9.0的二进制表示1001.0 -> 1.001 * 2^3 ，所以S = 0，M = 1.001，E = 3
    //由上面学习的浮点数存储方式可得：0 10000010 00100000000000000000000(浮点型存储9.0)
    //此时以%f打印就是浮点数取的过程，所以第四个输出结果得到的值就是9.000000
    //而%d打印是将其视为有符号数，由于最高位为0，则为正数，正数补码和原码相同，
    //则就是把 01000001000100000000000000000000 直接转换成十进制输出，所以第三个输出结果得到的值为1091567616